bcv_passage.coffee | |
|---|---|
| This class takes the output from the grammar and turns it into simpler objects for additional processing or for output. | class bcv_passage
books: []
indices: {} |
|
| options: {}
translations: {} |
PublicLoop through the parsed passages. | handle_array: (passages, accum=[], context={}) -> |
|
| for passage in passages |
| Each | [accum, context] = @handle_obj passage, accum, context
[accum, context] |
| Handle a typical passage object with an | handle_obj: (passage, accum, context) ->
if passage.type? and @[passage.type]?
@[passage.type] passage, accum, context
else [accum, context] |
Types Returned from the GrammarThese functions correspond to Handle a book on its own. | b: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.passages = []
alternates = []
for b in @books[passage.value].parsed
valid = @validate_ref passage.start_context.translations, {b: b}
obj = start: {b: b}, end: {b: b}, valid: valid |
| Use the first valid book. | if passage.passages.length is 0 and valid.valid
passage.passages.push obj
else
alternates.push obj |
| If none are valid, use the first one. | passage.passages.push alternates.shift() if passage.passages.length is 0
passage.passages[0].alternates = alternates if alternates.length > 0
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
passage.absolute_indices ?= @get_absolute_indices passage.indices
accum.push passage
context = b: passage.passages[0].start.b
context.translations = passage.start_context.translations if passage.start_context.translations?
[accum, context] |
| Handle book-only ranges. | b_range: (passage, accum, context) ->
@range passage, accum, context |
| Handle book-only ranges like 1-2 Samuel. It doesn't support multiple ambiguous ranges (like | b_range_pre: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.passages = []
alternates = []
book = @pluck "b", passage.value
[[end], context] = @b book, [], context
passage.absolute_indices ?= @get_absolute_indices passage.indices
start_obj = b: passage.value[0].value + end.passages[0].start.b.substr(1), type: "b"
passage.passages = [start: start_obj, end: end.passages[0].end, valid: end.passages[0].valid]
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
accum.push passage
[accum, context] |
| The base (root) object in the grammar and controls the base indices. | base: (passage, accum, context) ->
@indices = @calculate_indices passage.match, passage.start_index
@handle_array passage.value, accum, context |
| Handle book-chapter. | bc: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.passages = []
for type in ["b", "c", "v"]
delete context[type]
c = @pluck("c", passage.value).value
alternates = []
for b in @books[@pluck("b", passage.value).value].parsed
context_key = "c"
valid = @validate_ref passage.start_context.translations, {b: b, c: c}
obj = start: {b: b}, end: {b: b}, valid: valid |
| Is it really a | if valid.messages.start_chapter_not_exist_in_single_chapter_book
obj.valid = @validate_ref passage.start_context.translations, {b: b, v: c}
obj.start.c = 1
obj.end.c = 1
context_key = "v"
obj.start[context_key] = c |
| If it's zero, fix it before assigning the end. | [obj.start.c, obj.start.v] = @fix_start_zeroes obj.valid, obj.start.c, obj.start.v
obj.end[context_key] = obj.start[context_key]
if passage.passages.length is 0 and obj.valid.valid
passage.passages.push obj
else
alternates.push obj
passage.passages.push alternates.shift() if passage.passages.length is 0
passage.passages[0].alternates = alternates if alternates.length > 0
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
passage.absolute_indices ?= @get_absolute_indices passage.indices
for type in ["b", "c", "v"]
context[type] = passage.passages[0].start[type] if passage.passages[0].start[type]?
accum.push passage
[accum, context] |
| Handle "Ps 3 title" | bc_title: (passage, accum, context) ->
passage.start_context = @shallow_clone context |
| First, check to see whether we're dealing with Psalms. If not, treat it as a straight | [[bc], context] = @bc @pluck("bc", passage.value), [], context
if bc.passages[0].start.b isnt "Ps" and bc.passages[0].alternates?
for i in [0...bc.passages[0].alternates.length]
continue unless bc.passages[0].alternates[i].start.b is "Ps" |
| If Psalms is one of the alternates, promote it to the primary passage and discard the others--we know it's right. | bc.passages[0] = bc.passages[0].alternates[i]
break
if bc.passages[0].start.b isnt "Ps"
accum.push bc
return [accum, context] |
| Overwrite all the other book possibilities; the presence of "title" indicates a Psalm | @books[@pluck("b", bc.value).value].parsed = ["Ps"] |
| Set the | title = @pluck "title", passage.value
passage.value[1] = {type: "v", value: [{type: "integer", value: 1, indices: title.indices}], indices: title.indices} |
| Not for reparsing but in case we're curious later. | passage.original_type = "bc_title"
passage.type = "bcv" |
| Treat it as a standard | @bcv passage, accum, passage.start_context |
| Handle book chapter:verse. | bcv: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.passages = []
for type in ["b", "c", "v"]
delete context[type]
bc = @pluck "bc", passage.value
c = @pluck("c", bc.value).value
v = @pluck("v", passage.value).value
alternates = []
for b in @books[@pluck("b", bc.value).value].parsed
valid = @validate_ref passage.start_context.translations, {b: b, c: c, v: v}
[c, v] = @fix_start_zeroes valid, c, v
obj = start: {b: b, c: c, v: v}, end: {b: b, c: c, v: v}, valid: valid |
| Use the first valid option. | if passage.passages.length is 0 and valid.valid
passage.passages.push obj
else
alternates.push obj |
| If there are no valid options, use the first one. | passage.passages.push alternates.shift() if passage.passages.length is 0
passage.passages[0].alternates = alternates if alternates.length > 0
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
passage.absolute_indices ?= @get_absolute_indices passage.indices |
| Set all the available context keys. | for type in ["b", "c", "v"]
context[type] = passage.passages[0].start[type] if passage.passages[0].start[type]?
accum.push passage
[accum, context] |
| Handle "Philemon verse 6." This is unusual. | bv: (passage, accum, context) ->
passage.start_context = @shallow_clone context
[b, v] = passage.value |
| Construct a virtual BCV object with a chapter of 1. | bcv =
indices: passage.indices
value: [
{type: "bc", value: [b, {type: "c", value: [{type: "integer", value: 1}]}]}
v
]
[[bcv], context] = @bcv bcv, [], context
passage.passages = bcv.passages
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
passage.absolute_indices ?= @get_absolute_indices passage.indices
accum.push passage
[accum, context] |
| Handle a chapter. | c: (passage, accum, context) ->
passage.start_context = @shallow_clone context |
| If it's an actual chapter object, the value we want is in the integer object inside it. | c = if passage.type is "integer" then passage.value else @pluck("integer", passage.value).value
valid = @validate_ref passage.start_context.translations, {b: context.b, c: c} |
| If it's a single-chapter book, then treat it as a verse even if it looks like a chapter (unless its value is | if not valid.valid and valid.messages.start_chapter_not_exist_in_single_chapter_book
return @v passage, accum, context
[c] = @fix_start_zeroes valid, c
passage.passages = [start: {b: context.b, c: c}, end: {b: context.b, c: c}, valid: valid]
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
accum.push passage
context.c = c
delete context.v
passage.absolute_indices ?= @get_absolute_indices passage.indices
[accum, context] |
| Handle "23rd Psalm" by recasting it as a | c_psalm: (passage, accum, context) ->
passage.original_type = passage.type
passage.original_value = passage.value
passage.type = "bc" |
| This string always starts with the chapter number, followed by other letters. | c = @books[passage.value].value.match(/^\d+/)[0]
passage.value = [
{type: "b", value: passage.original_value, indices: passage.indices}
{type: "c", value: [{type: "integer", value: c, indices: passage.indices}], indices: passage.indices}
]
@bc passage, accum, context |
| Handle "Ps 3, ch 4:title" | c_title: (passage, accum, context) ->
passage.start_context = @shallow_clone context |
| If it's not a Psalm, treat it as a regular chapter | if context.b isnt "Ps"
return @c passage.value[0], accum, context |
| Add a | title = @pluck "title", passage.value
passage.value[1] = {type: "v", value: [{type: "integer", value: 1, indices: title.indices}], indices: title.indices} |
| Not for reparsing but in case we're curious later. | passage.original_type = "c_title"
passage.type = "cv"
@cv passage, accum, passage.start_context |
| Handle a chapter:verse. | cv: (passage, accum, context) ->
passage.start_context = @shallow_clone context
c = @pluck("c", passage.value).value
v = @pluck("v", passage.value).value
valid = @validate_ref passage.start_context.translations, {b: context.b, c: c, v: v}
[c, v] = @fix_start_zeroes valid, c, v
passage.passages = [start: {b: context.b, c: c, v: v}, end: {b: context.b, c: c, v: v}, valid: valid]
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
accum.push passage
context.c = c
context.v = v
passage.absolute_indices ?= @get_absolute_indices passage.indices
[accum, context] |
| Handle "Chapters 1-2 from Daniel". | cb_range: (passage, accum, context) ->
passage.original_type = passage.type
passage.type = "range"
[b, start_c, end_c] = passage.value
passage.original_value = [b, start_c, end_c]
passage.value = [{type: "bc", value:[b, start_c], indices: passage.indices}, end_c]
end_c.indices[1] = passage.indices[1]
@range passage, accum, context |
| Handle "23rd Psalm verse 1" by recasting it as a | cv_psalm: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.original_type = passage.type
passage.original_value = passage.value
[c_psalm, v] = passage.value
passage.type = "bcv"
[[bc]] = @c_psalm c_psalm, [], passage.start_context
passage.value = [bc, v]
@bcv passage, accum, context |
| Handle "and following" (e.g., "Matt 1:1ff") by assuming it means to continue to the end of the current context (end of chapter if a verse is given, end of book if a chapter is given). | ff: (passage, accum, context) ->
passage.start_context = @shallow_clone context |
| Create a virtual end to pass to | passage.value.push type: "integer", indices: passage.indices, value: 999
[[passage], context] = @range passage, [], passage.start_context |
| And then get rid of the virtual end so it doesn't stick around if we need to reparse it later. | passage.value.pop() |
| Ignore any warnings that the end chapter / verse doesn't exist. | delete passage.passages[0].valid.end_verse_not_exist if passage.passages[0].valid.end_verse_not_exist?
delete passage.passages[0].valid.end_chapter_not_exist if passage.passages[0].valid.end_chapter_not_exist?
delete passage.passages[0].end.original_c if passage.passages[0].end.original_c? |
|
| accum.push passage
passage.absolute_indices ?= @get_absolute_indices passage.indices
[accum, context] |
| Handle "Ps 3-4:title" or "Acts 2:22-27. Title" | integer_title: (passage, accum, context) ->
passage.start_context = @shallow_clone context |
| If it's not Psalms, treat it as a straight integer, ignoring the "title". | if context.b isnt "Ps"
return @integer passage.value[0], accum, context
passage.value[0] = type: "c", value: [passage.value[0]], indices: [passage.value[0].indices[0], passage.value[0].indices[1]] |
| Add a | v_indices = [passage.indices[1] - 5, passage.indices[1]]
passage.value[1] = {type: "v", value: [{type: "integer", value: 1, indices: v_indices}], indices: v_indices} |
| Not for reparsing but in case we're curious later. | passage.original_type = "integer_title"
passage.type = "cv"
@cv passage, accum, passage.start_context |
| Pass the integer off to whichever handler is relevant. | integer: (passage, accum, context) ->
return @v passage, accum, context if context.v?
return @c passage, accum, context |
| Handle a sequence of references. This is the only function that can return more than one object in the | sequence: (passage, accum, context) ->
passage.start_context = @shallow_clone context
passage.passages = []
for obj in passage.value
[[psg], context] = @handle_array obj, [], context |
| There's only more than one | for sub_psg in psg.passages
sub_psg.type ?= psg.type |
| Add the indices so we can possibly retrieve them later, depending on our | sub_psg.absolute_indices ?= psg.absolute_indices
sub_psg.translations = psg.start_context.translations if psg.start_context.translations?
passage.passages.push sub_psg
unless passage.absolute_indices?
if passage.passages.length > 0
passage.absolute_indices = [passage.passages[0].absolute_indices[0], passage.passages[passage.passages.length - 1].absolute_indices[1]]
else
passage.absolute_indices = @get_absolute_indices passage.indices
accum.push passage
[accum, context] |
| Handle a verse, either as part of a sequence or because someone explicitly wrote "verse". | v: (passage, accum, context) ->
v = if passage.type is "integer" then passage.value else @pluck("integer", passage.value).value
passage.start_context = @shallow_clone context |
| The chapter context might not be set if it follows a book in a sequence | c = if context.c? then context.c else 1
valid = @validate_ref passage.start_context.translations, {b: context.b, c: c, v: v}
[no_c, v] = @fix_start_zeroes valid, 0, v
passage.passages = [start: {b: context.b, c: c, v: v}, end: {b: context.b, c: c, v: v}, valid: valid]
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
passage.absolute_indices ?= @get_absolute_indices passage.indices
accum.push passage
context.v = v
[accum, context] |
RangesHandle any type of start and end range. | range: (passage, accum, context) ->
passage.start_context = @shallow_clone context
[start, end] = passage.value |
| Matt 5-verse 6 = Matt.5.6 | if end.type is "v" and (start.type is "bc" or start.type is "c") and @options.end_range_digits_strategy is "verse"
return @range_change_integer_end passage, accum |
| These always return exactly one object that we're interested in. | [[start], context] = @handle_obj start, [], context
[[end], context] = @handle_obj end, [], context |
| If we had to change the start or end | passage.value = [start, end] |
| Similarly, if we had to adjust the indices, make sure they're reflected in the indices for the range. | passage.indices = [start.indices[0], end.indices[1]] |
| We'll also need to recalculate these if they exist. | delete passage.absolute_indices |
| Create the prospective start and end objects that will end up in | start_obj = b: start.passages[0].start.b, c: start.passages[0].start.c, v: start.passages[0].start.v, type: start.type
end_obj = b: end.passages[0].end.b, c: end.passages[0].end.c, v: end.passages[0].end.v, type: end.type
end_obj.c = 0 if end.passages[0].valid.messages.start_chapter_is_zero
end_obj.v = 0 if end.passages[0].valid.messages.start_verse_is_zero
valid = @validate_ref passage.start_context.translations, start_obj, end_obj
if valid.valid |
| If Heb 13-15, treat it as Heb 13:15. This may be too clever for its own good. | if valid.messages.end_chapter_not_exist and @options.end_range_digits_strategy is "verse" and not start_obj.v? and (end.type is "integer" or end.type is "v")
temp_value = if end.type is "v" then @pluck "integer", end.value else end.value
temp_valid = @validate_ref passage.start_context.translations, {b: start_obj.b, c: start_obj.c, v: temp_value}
return @range_change_integer_end passage, accum if temp_valid.valid |
| If "John 10:22-42 vs 27", we're possibly misreading the "42 vs 27" as a | if valid.messages.end_chapter_not_exist and @options.end_range_digits_strategy is "verse" and start_obj.v? and end.type is "cv" |
| Make sure that what we're changing it to actually exists (that the chapter number can become the verse number, and the verse number is also valid in the current chapter). | temp_valid = @validate_ref passage.start_context.translations, {b:end_obj.b, c: start_obj.c, v: end_obj.c}
temp_valid = @validate_ref passage.start_context.translations, {b:end_obj.b, c: start_obj.c, v: end_obj.v} if temp_valid.valid
return @range_change_cv_end passage, accum if temp_valid.valid |
| Otherwise, snap start/end chapters/verses if they're too high or low. | @range_validate valid, start_obj, end_obj, passage
else |
| Is it not valid because the end is before the start and the Only go with a | if ((valid.messages.end_chapter_before_start or valid.messages.end_verse_before_start) and (end.type is "integer" or end.type is "v") or (valid.messages.end_chapter_before_start and end.type is "cv"))
new_end = @range_check_new_end passage.start_context.translations, start_obj, end_obj, valid |
| If that's the case, then reparse the current passage object after correcting the end value, which is an integer. | return @range_change_end passage, accum, new_end if new_end > 0 |
| If someone enters "Jer 33-11", they probably mean "Jer.33.11"; as above, this may be too clever for its own good. | if @options.end_range_digits_strategy is "verse" and start_obj.v is undefined and (end.type is "integer" or end.type is "v")
temp_value = if end.type is "v" then @pluck "integer", end.value else end.value
temp_valid = @validate_ref passage.start_context.translations, {b: start_obj.b, c: start_obj.c, v: temp_value}
return @range_change_integer_end passage, accum if temp_valid.valid |
| Otherwise, if we couldn't fix the range, then treat the range as a sequence. | [passage.original_type, passage.type] = [passage.type, "sequence"] |
| Construct the sequence value in the format expected. | [passage.original_value, passage.value] = [[start, end], [[start], [end]]] |
| Don't use the | return @handle_obj passage, accum, passage.start_context |
| We've already reset the indices to match the indices of the contained objects. | passage.absolute_indices ?= @get_absolute_indices passage.indices
passage.passages = [start: start_obj, end: end_obj, valid: valid]
passage.passages[0].translations = passage.start_context.translations if passage.start_context.translations?
accum.push passage
[accum, context] |
| For Ps 122-23, treat the 23 as 123. | range_change_end: (passage, accum, new_end) ->
[start, end] = passage.value
if end.type is "integer"
end.original_value = end.value
end.value = new_end
else if end.type is "v"
new_obj = @pluck "integer", end.value
new_obj.original_value = new_obj.value
new_obj.value = new_end
else if end.type is "cv" |
| Get the chapter object and assign it (in place) the new value. | new_obj = @pluck "c", end.value
new_obj.original_value = new_obj.value
new_obj.value = new_end
@handle_obj passage, accum, passage.start_context |
| For "Jer 33-11", treat the "11" as a verse. | range_change_integer_end: (passage, accum) ->
[start, end] = passage.value
passage.original_type = passage.type
passage.original_value = [start, end] |
| The start.type is only bc, c, or integer; we're just adding a v for the first two. | passage.type = if start.type is "integer" then "cv" else start.type + "v" |
| Create the object in the expected format if it's not already a verse. | passage.value[0] = {type: "c", value: [start], indices: start.indices} if start.type is "integer"
passage.value[1] = {type: "v", value: [end], indices: end.indices} if end.type is "integer"
@handle_obj passage, accum, passage.start_context |
| In cases like "John 10:22-42 vs 27", treat it as "10:22-42,47" instead of "10:22-42:27". | range_change_cv_end: (passage, accum) ->
[start, end] = passage.value
passage.original_type = passage.type
passage.original_value = [start, end]
passage.type = "sequence"
[new_range_end, new_sequence_end] = end.value |
| If a translation sequence needs to come back here and reuse it, make sure it can get the old object ( | new_range_end = @shallow_clone new_range_end |
| Was "c" but change it to "v" to serve as the end of a range. | new_range_end.original_type = new_range_end.type
new_range_end.type = "v" |
| Change it into a sequence consisting of a range and a free verse. | passage.value = [
[{type: "range", value: [start, new_range_end], indices: [start.indices[0], new_range_end.indices[1]]}]
[new_sequence_end]
]
@sequence passage, accum, passage.start_context
range_validate: (valid, start_obj, end_obj, passage) -> |
| If it's valid but the end range goes too high, snap it back to the appropriate chapter or verse. | if valid.messages.end_chapter_not_exist |
|
| end_obj.original_c = end_obj.c
end_obj.c = valid.messages.end_chapter_not_exist |
| If we've snapped it back to the last chapter and there's a verse, also snap to the end of that chapter. If we've already overshot the chapter, there's no reason to think we've gotten the verse right; Gen 50:1-51:1 = Gen 50:1-26 = Gen 50. If there's no verse, we don't need to worry about it. | if end_obj.v? |
|
| end_obj.v = @validate_ref(passage.start_context.translations, {b: end_obj.b, c: end_obj.c, v: 999}).messages.end_verse_not_exist |
| If the end verse is too high, snap back to the maximum verse. | else if valid.messages.end_verse_not_exist
end_obj.original_v = end_obj.v
end_obj.v = valid.messages.end_verse_not_exist
end_obj.v = valid.messages.end_verse_is_zero if valid.messages.end_verse_is_zero and @options.zero_verse_strategy isnt "allow"
end_obj.c = valid.messages.end_chapter_is_zero if valid.messages.end_chapter_is_zero
[start_obj.c, start_obj.v] = @fix_start_zeroes valid, start_obj.c, start_obj.v
true |
| If the start chapter or verse is 0, convert it to a 1. | fix_start_zeroes: (valid, c, v) ->
if valid.valid
c = valid.messages.start_chapter_is_zero if valid.messages.start_chapter_is_zero
v = valid.messages.start_verse_is_zero if valid.messages.start_verse_is_zero and @options.zero_verse_strategy isnt "allow"
[c, v] |
| If a new end chapter/verse in a range may be necessary, calculate it. | range_check_new_end: (translations, start_obj, end_obj, valid) ->
new_end = 0
type = null |
| See whether a digit might be omitted (e.g., Gen 22-4 = Gen 22-24) | if valid.messages.end_chapter_before_start then type = "c"
else if valid.messages.end_verse_before_start then type = "v"
new_end = @range_get_new_end_value(start_obj, end_obj, valid, type) if type?
if new_end > 0
obj_to_validate = b: end_obj.b, c: end_obj.c, v: end_obj.v
obj_to_validate[type] = new_end
new_valid = @validate_ref translations, obj_to_validate
new_end = 0 unless new_valid.valid
new_end |
| If a sequence has an end chapter/verse that's before the the start, check to see whether it can be salvaged: Gen 28-9 = Gen 28-29; Ps 101-24 = Ps 101-124. The | range_get_new_end_value: (start_obj, end_obj, valid, key) -> |
| Return 0 unless it's salvageable. | new_end = 0
return new_end if ((key is "c" and valid.messages.end_chapter_is_zero) or (key is "v" and valid.messages.end_verse_is_zero)) |
| 54-5, not 54-43, 54-3, or 54-4. | if start_obj[key] >= 10 and end_obj[key] < 10 and start_obj[key] - 10 * Math.floor(start_obj[key] / 10) < end_obj[key] |
| Add the start tens digit to the original end value: 54-5 = 54 through 50 + 5. | new_end = end_obj[key] + 10 * Math.floor(start_obj[key] / 10) |
| 123-40, not 123-22 or 123-23; 123-4 is taken care of in the first case. | else if start_obj[key] >= 100 and end_obj[key] < 100 and start_obj[key] - 100 < end_obj[key] |
| Add 100 to the original end value: 100-12 = 100 through 100 + 12. | new_end = end_obj[key] + 100
new_end |
TranslationsEven a single translation ("NIV") appears as part of a translation sequence. Here we handle the sequence and apply the translations to any previous passages lacking an explicit translation: in "Matt 1, 5 ESV," both | translation_sequence: (passage, accum, context) ->
translations = [] |
| First get all the translations in the sequence; the first one is separate from the others (which may not exist). | translations.push translation: @books[passage.value[0].value].parsed
for val in passage.value[1] |
|
| val = @books[@pluck("translation", val).value].parsed |
| And now | translations.push translation: val if val? |
| We need some metadata to do this right. | for translation in translations |
| Do we know anything about this translation? If so, use that. If not, use the default | alias = if @translations.aliases[translation.translation]? then translation.translation else "default" |
|
| translation.osis = @translations.aliases[alias].osis |
|
| translation.alias = @translations.aliases[alias].alias |
| Now we need to go back and find the earliest already-parsed passage without a translation. We start with 0 because the below loop will never yield a 0. | if accum.length > 0
use_i = 0 |
| Start with the most recent and go backward--we don't want to overlap another | for i in [accum.length - 1 .. 0] |
| With a new translation comes the possibility that a previously invalid reference will become valid, so reset it to its original type. For example, a multi-book range may be correct in a different translation because the books are in a different order. | accum[i].type = accum[i].original_type if accum[i].original_type?
accum[i].value = accum[i].original_value if accum[i].original_value?
continue unless accum[i].type == "translation_sequence" |
| If we made it here, then we hit a translation sequence, and we know that the item following it is the first one we care about. | use_i = i + 1
break |
| Include the translations in the start context.
| if use_i < accum.length
accum[use_i].start_context.translations = translations |
| The objects in accum are replaced in-place, so we don't need to try to merge them back. We re-parse them because the translation may cause previously valid (or invalid) references to flip the other way--if the new translation includes (or doesn't) the Deuterocanonicals, for example. We ignore the | [new_accum, context] = @handle_array accum.slice(use_i), [], accum[use_i].start_context |
| We may need these indices later, depending on how we want to output the data. | passage.absolute_indices ?= @get_absolute_indices passage.indices |
| Include the | accum.push passage |
| Don't carry over the translations into any later references; translations only apply backwards. | delete context.translations
[accum, context] |
UtilitiesPluck the object or value matching a type from an array. | pluck: (type, passages) ->
for passage in passages
continue unless passage.type? and passage.type is type
return @pluck("integer", passage.value) if type is "c" or type is "v"
return passage
null |
| Make a shallow clone of an object. Nested objects are referenced, not cloned. | shallow_clone: (obj) ->
out = {}
for own key, val of obj
out[key] = val
out |
| Split a string on a regexp. This is purely for cross-browser (read: IE) compatibility, which | |
| Given a string and initial index, calculate indices for parts of the string. For example, a string that starts at index 10 might have a book that pushes it to index 12 starting at its third character. | calculate_indices: (match, adjust) -> |
| This gets switched out the first time in the loop; the first item is never a book even if a book is the first part of the string--there's an empty string before it. | switch_type = "book"
indices = []
match_index = 0
adjust = parseInt adjust, 10 |
| It would be easier to do | parts = [match]
for character in ["\x1e", "\x1f"]
temp = []
for part in parts
temp = temp.concat part.split(character)
parts = temp
for part in parts |
| Start off assuming it's not a book. | switch_type = if switch_type is "book" then "rest" else "book" |
| Empty strings don't move the index. This could happen with consecutive books. | part_length = part.length
continue if part_length == 0 |
| If it's a book, then get the start index of the actual book, add the length of the actual string, then subtract the length of the integer id and the two surrounding characters. | if switch_type == "book" |
| Remove any stray extra indicators. | part = part.replace /\/[a-z]$/, "" |
| Get the length of the id + the surrounding characters. We want the | end_index = match_index + part_length
if indices.length > 0 and indices[indices.length - 1].index == adjust
indices[indices.length - 1].end = end_index
else
indices.push start: match_index, end: end_index, index: adjust |
| If the part is one character (three characters total) starting at | match_index += part_length + 2 |
| Use the known | adjust = @books[part].start_index + @books[part].value.length - match_index
indices.push start: end_index + 1, end: end_index + 1, index: adjust
else |
| The | end_index = match_index + part_length - 1
if indices.length > 0 and indices[indices.length - 1].index == adjust
indices[indices.length - 1].end = end_index
else
indices.push start: match_index, end: end_index, index: adjust
match_index += part_length
indices |
| Find the absolute string indices of start and end points. | get_absolute_indices: ([start, end]) ->
start_out = null
end_out = null |
|
| for index in @indices |
| If we haven't found the absolute start index yet, set it. | if start_out is null and index.start <= start <= index.end
start_out = start + index.index |
| This may be in the same loop iteration as | if index.start <= end <= index.end
end_out = end + index.index + 1
break
[start_out, end_out] |
ValidatorsGiven a start and optional end bcv object, validate that the verse exists and is valid. It returns an array with validity for each translations. | validate_ref: (translations, start, end) -> |
| The | translations or= [{translation: "default", osis: "", alias: "default"}]
translation = translations[0] |
| Only true if | return {valid: false, messages: {translation_invalid: true}} unless translation?
valid = true
messages = {} |
|
| translation.alias ?= "default" |
| Only true if | return {valid: false, messages: {translation_invalid: true}} unless translation.alias? |
| Not a fatal error because we assume that translations match the default unless we know differently. But we still record it because we may want to know about it later. | unless @translations[translation.alias]?
translation.alias = "default"
messages.translation_unknown = true
[valid, messages] = @validate_start_ref translation.alias, start, valid, messages
[valid, messages] = @validate_end_ref translation.alias, start, end, valid, messages if end
valid: valid, messages: messages |
| Make sure that the start ref exists in the given translation. | validate_start_ref: (translation, start, valid, messages) -> |
| Matt | if @translations[translation].order[start.b]?
start.c ?= 1
start.c = parseInt start.c, 10 |
| Matt five | if isNaN start.c
valid = false
messages.start_chapter_not_numeric = true
return [valid, messages] |
| Matt 0 | if start.c == 0
messages.start_chapter_is_zero = 1
if @options.zero_chapter_strategy is "error" then valid = false
else start.c = 1 |
| Matt 5 | if start.c > 0 and @translations[translation].chapters[start.b][start.c - 1]? |
| Matt 5:10 | if start.v?
start.v = parseInt start.v, 10 |
| Matt 5:ten | if isNaN start.v
valid = false
messages.start_verse_not_numeric = true |
| Matt 5:0 | else if start.v == 0
messages.start_verse_is_zero = 1
if @options.zero_verse_strategy is "error" then valid = false
else if @options.zero_verse_strategy is "upgrade" then start.v = 1 |
| Matt 5:100 | else if start.v > @translations[translation].chapters[start.b][start.c - 1]
valid = false
messages.start_verse_not_exist = @translations[translation].chapters[start.b][start.c - 1] |
| Matt 50 | else
valid = false
if start.c != 1 and @translations[translation].chapters[start.b].length == 1
messages.start_chapter_not_exist_in_single_chapter_book = 1
else if start.c > 0
messages.start_chapter_not_exist = @translations[translation].chapters[start.b].length |
| None 2:1 | else
valid = false
messages.start_book_not_exist = true
[valid, messages] |
| The end ref pretty much just has to be after the start ref; beyond the book, we don't require that the chapter or verse exists. This is useful when people get end verses wrong. | validate_end_ref: (translation, start, end, valid, messages) ->
end.c = parseInt end.c, 10 if end.c?
end.v = parseInt end.v, 10 if end.v? |
| Matt 0 | if end.c? and not(isNaN end.c) and end.c == 0
messages.end_chapter_is_zero = 1
if @options.zero_chapter_strategy is "error" then valid = false
else end.c = 1 |
| Matt-Mark | if @translations[translation].order[end.b]? |
| Mark 4-Matt 5, None 4-Matt 5 | if @translations[translation].order[start.b]? and @translations[translation].order[start.b] > @translations[translation].order[end.b]
valid = false
messages.end_book_before_start = true |
| Matt 5-6 | if start.b == end.b and end.c? and not isNaN end.c |
| Matt-Matt 4 | start.c ?= 1 |
| Matt 5-4 | if not isNaN(parseInt start.c, 10) and start.c > end.c
valid = false
messages.end_chapter_before_start = true |
| Matt 5:7-5:8 | else if start.c == end.c and end.v? and not isNaN end.v |
| Matt 5-5:8 | start.v ?= 1 |
| Matt 5:8-7 | if not isNaN(parseInt start.v, 10) and start.v > end.v
valid = false
messages.end_verse_before_start = true
if end.c? and not isNaN end.c
if not @translations[translation].chapters[end.b][end.c - 1]?
if @translations[translation].chapters[end.b].length is 1
messages.end_chapter_not_exist_in_single_chapter_book = 1
else if end.c > 0
messages.end_chapter_not_exist = @translations[translation].chapters[end.b].length
if end.v? and not isNaN end.v
end.c ?= @translations[translation].chapters[end.b].length
if end.v > @translations[translation].chapters[end.b][end.c - 1]
messages.end_verse_not_exist = @translations[translation].chapters[end.b][end.c - 1]
else if end.v == 0
messages.end_verse_is_zero = 1
if @options.zero_verse_strategy is "error" then valid = false
else if @options.zero_verse_strategy is "upgrade" then end.v = 1 |
| Matt 5:1-None 6 | else
valid = false
messages.end_book_not_exist = true |
| Matt 2-four | if end.c? and isNaN end.c
valid = false
messages.end_chapter_not_numeric = true |
| Matt 5:7-eight | if end.v? and isNaN end.v
valid = false
messages.end_verse_not_numeric = true
[valid, messages]
|