openbibleinfo / Bible-Passage-Reference-Parser

Coffeescript to identify and understand Bible references like "John 3:16."
223 stars 65 forks source link

OSIS .toString #7

Closed jamlen closed 10 years ago

jamlen commented 10 years ago

Is there a method available on the OSIS to give it back in a human readable format?

For example: '1Cor.2.5' => '1 Corinthians 2:5'

openbibleinfo commented 10 years ago

For now you can do something like the following, where you call osis_to_readable("long", "1Cor.2.5"). It only works on a single OSIS (a book, chapter, verse, or range), not a sequence of several OSISes.

bcv_parser = require("./en_bcv_parser.js").bcv_parser
bcv = new bcv_parser
bcv.set_options osis_compaction_strategy: "bcv", book_alone_strategy: "full", book_sequence_strategy: "include", consecutive_combination_strategy: "combine", book_range_strategy: "include"

abbrevs =
    "Gen":    ["Genesis","Gen","Ge"]
    "Exod":   ["Exodus","Exod","Ex"]
    "Lev":    ["Leviticus","Lev","Lv"]
    "Num":    ["Numbers","Num","Nu"]
    "Deut":   ["Deuteronomy","Deut","Dt"]
    "Josh":   ["Joshua","Josh"]
    "Judg":   ["Judges","Judg","Jg"]
    "Ruth":   ["Ruth","Ruth","Rt"]
    "1Sam":   ["1 Samuel","1 Sam","1 Sa"]
    "2Sam":   ["2 Samuel","2 Sam","2 Sa"]
    "1Kgs":   ["1 Kings","1 Kgs","1 Kg"]
    "2Kgs":   ["2 Kings","2 Kgs","2 Kg"]
    "1Chr":   ["1 Chronicles","1 Chr","1 Ch"]
    "2Chr":   ["2 Chronicles","2 Chr","2 Ch"]
    "Ezra":   ["Ezra","Ezra","Ezr"]
    "Neh":    ["Nehemiah","Neh"]
    "Esth":   ["Esther","Esth","Est"]
    "Job":    ["Job"]
    "Ps":     ["Psalms","Ps","Ps","Psalm"]
    "Prov":   ["Proverbs","Prov","Pr"]
    "Eccl":   ["Ecclesiastes","Eccl","Ec"]
    "Song":   ["Song of Songs","Song","Sg"]
    "Isa":    ["Isaiah","Isa","Is"]
    "Jer":    ["Jeremiah","Jer","Je"]
    "Lam":    ["Lamentations","Lam","La"]
    "Ezek":   ["Ezekiel","Ezek","Ezk"]
    "Dan":    ["Daniel","Dan","Dn"]
    "Hos":    ["Hosea","Hos","Ho"]
    "Joel":   ["Joel","Joel","Jl"]
    "Amos":   ["Amos","Amos","Am"]
    "Obad":   ["Obadiah","Obad","Ob"]
    "Jonah":  ["Jonah","Jonah","Jon"]
    "Mic":    ["Micah","Mic"]
    "Nah":    ["Nahum","Nah","Na"]
    "Hab":    ["Habakkuk","Hab"]
    "Zeph":   ["Zephaniah","Zeph","Zph"]
    "Hag":    ["Haggai","Hag"]
    "Zech":   ["Zechariah","Zech","Zch"]
    "Mal":    ["Malachi","Mal"]
    "Matt":   ["Matthew","Matt","Mt"]
    "Mark":   ["Mark","Mark","Mk"]
    "Luke":   ["Luke","Luke","Lk"]
    "John":   ["John","John","Jn"]
    "Acts":   ["Acts","Acts","Ac"]
    "Rom":    ["Romans","Rom","Ro"]
    "1Cor":   ["1 Corinthians","1 Cor","1 Co"]
    "2Cor":   ["2 Corinthians","2 Cor","2 Co"]
    "Gal":    ["Galatians","Gal"]
    "Eph":    ["Ephesians","Eph"]
    "Phil":   ["Philippians","Phil"]
    "Col":    ["Colossians","Col"]
    "1Thess": ["1 Thessalonians","1 Thess","1 Th"]
    "2Thess": ["2 Thessalonians","2 Thess","2 Th"]
    "1Tim":   ["1 Timothy","1 Tim","1 Ti"]
    "2Tim":   ["2 Timothy","2 Tim","2 Ti"]
    "Titus":  ["Titus"]
    "Phlm":   ["Philemon","Phlm","Phm"]
    "Heb":    ["Hebrews","Heb"]
    "Jas":    ["James","Jas"]
    "1Pet":   ["1 Peter","1 Pet","1 Pt"]
    "2Pet":   ["2 Peter","2 Pet","2 Pt"]
    "1John":  ["1 John","1 John","1 Jn"]
    "2John":  ["2 John","2 John","2 Jn"]
    "3John":  ["3 John","3 John","3 Jn"]
    "Jude":   ["Jude"]
    "Rev":    ["Revelation","Rev","Rv"]
    "Tob":    ["Tobit","Tob"]
    "Jdt":    ["Judith","Jdt"]
    "GkEsth": ["Greek Esther","Gk Esth","Gk Est"]
    "Wis":    ["Wisdom","Wis"]
    "Sir":    ["Sirach","Sir"]
    "Bar":    ["Baruch","Bar"]
    "PrAzar": ["Prayer of Azariah","Pr Azar"]
    "Sus":    ["Susannah","Sus"]
    "Bel":    ["Bel and the Dragon","Bel"]
    "SgThree": ["Song of the Three Young Men","SgThree"]
    "EpJer":  ["Epistle of Jeremiah","Ep Jer"]
    "1Macc":  ["1 Maccabees","1 Macc","1 Mc"]
    "2Macc":  ["2 Maccabees","2 Macc","2 Mc"]
    "3Macc":  ["3 Maccabees","3 Macc","3 Mc"]
    "4Macc":  ["4 Maccabees","4 Macc","4 Mc"]
    "1Esd":   ["1 Esdras","1 Esd"]
    "2Esd":   ["2 Esdras","2 Esd"]
    "PrMan":  ["Prayer of Manasseh","Pr Man"]

separators =
    "bc":       " "
    "bv":       " "
    "cv":       ":"
    "range_b":  "\u2014"
    "range_c":  "\u2014"
    "range_v":  "\u2013"
    "sequence": ", "

output_types =
    "long":    0
    "short":   1
    "shorter": 2
    "single":  3

single_chapter_books = {}

osis_to_readable = (output_type, osis) ->
    [start, end] = osis.split "-"
    if end?
        handle_range start, end, output_type
    else
        handle_single start, output_type

handle_range = (start, end, output_type) ->
    [sb, sc, sv] = start.split "."
    [eb, ec, ev] = end.split "."
    if sb is eb
        if sc is ec
            "#{handle_single(start, output_type, true, true)}#{separators.range_v}#{ev}"
        else
            if ev?
                start += ".1" unless sv?
                "#{handle_single(start, output_type, false, true)}#{separators.range_c}#{ec}#{separators.cv}#{ev}"
            else if sv?
                "#{handle_single(start, output_type, false, true)}#{separators.range_c}#{ec}#{separators.cv}#{get_end_verse eb, ec}"
            else
                "#{handle_single(start, output_type, false, true)}#{separators.range_c}#{ec}"
    else if ec?
        start += ".1" unless sc?
        start += ".1" if ev? and not sv?
        end += ".#{get_end_verse eb, ec}" if sv? and not ev?
        # Always `is_single_chapter=true` because we're in multiple books and we're always including the chapter (because `if ec?` is true).
        "#{handle_single(start, output_type, true, true)}#{separators.range_b}#{handle_single(end, output_type, true)}"
    else
        is_single_chapter = true
        if sc?
            ec = get_end_chapter eb
            end += ".#{ec}"
            end += ".#{get_end_verse eb, ec}" if sv?
        else is_single_chapter = false
        "#{handle_single(start, output_type, is_single_chapter, true)}#{separators.range_b}#{handle_single(end, output_type, is_single_chapter)}"

handle_single = (osis, output_type, is_single_chapter=true, is_range_start=false) ->
    [b, c, v] = osis.split "."
    if c?
        if v?
            if c is "1" and (v isnt "1" or is_range_start) and is_single_chapter_book(b)
                "#{get_best_book b, output_type, is_single_chapter}#{separators.bv}#{v}"
            else
                "#{get_best_book b, output_type, is_single_chapter}#{separators.bc}#{c}#{separators.cv}#{v}"
        else
            "#{get_best_book b, output_type, is_single_chapter}#{separators.bc}#{c}"
    else
        get_best_book b, output_type, is_single_chapter

get_best_book = (b, output_type, is_single_chapter) ->
    output_id = output_types[output_type] || 0
    while output_id > 0
        break if abbrevs[b][output_id]?
        output_id--
    output_id = output_types["single"] if is_single_chapter and output_id == 0 and abbrevs[b][output_types["single"]]?
    abbrevs[b][output_id]

is_single_chapter_book = (book) ->
    return single_chapter_books[book] if single_chapter_books[book]?
    osis = bcv.parse("#{book} 2").osis()
    [start, end] = osis.split "-"
    out = if end? then false else true
    single_chapter_books[book] = out
    out

get_end_chapter = (b) ->
    [start, end] = bcv.parse("#{b} 1-999").osis().split("-")
    [b, c, v] = end.split(".")
    c

get_end_verse = (b, c) ->
    [start, end] = bcv.parse("#{b} #{c}").osis().split("-")
    [b, c, v] = end.split(".")
    v

tests = [
    ["long", "Matt.2", "Matthew 2"]
    ["short", "Matt.2", "Matt 2"]
    ["shorter", "Matt.2", "Mt 2"]
    ["long", "Ps.2", "Psalm 2"]
    ["long", "Ps.2-Ps.47", "Psalms 2\u201447"]
    ["short", "Ps.2-Ps.47", "Ps 2\u201447"]
    ["shorter", "Ps.2-Ps.47", "Ps 2\u201447"]
    ["long", "Ps.2.3-Ps.4", "Psalms 2:3\u20144:8"]
    ["long", "Ps.2-Ps.4.3", "Psalms 2:1\u20144:3"]
    ["long", "Job-Ps", "Job\u2014Psalms"]
    ["long", "Job.21-Ps", "Job 21\u2014Psalm 150"]
    ["long", "Job.21-Ps.2", "Job 21\u2014Psalm 2"]
    ["long", "Job.21-Ps.2:4", "Job 21\u2014Psalm 2:4"]
    ["long", "Ps-Prov", "Psalms\u2014Proverbs"]
    ["shorter", "Ps-Prov", "Ps\u2014Pr"]
    ["long", "Ps.21-Prov", "Psalm 21\u2014Proverbs 31"]
    ["long", "Ps.21-Prov.2", "Psalm 21\u2014Proverbs 2"]
    ["long", "Ps.21-Prov.2.4", "Psalm 21:1\u2014Proverbs 2:4"]
    ["long", "Gen-Rev", "Genesis\u2014Revelation"]
    ["long", "Gen-Rev.22", "Genesis 1\u2014Revelation 22"]
    ["long", "Gen-Rev.22.21", "Genesis 1:1\u2014Revelation 22:21"]
    ["long", "Gen.1-Rev", "Genesis 1\u2014Revelation 22"]
    ["short", "Gen.1.1-Rev", "Gen 1:1\u2014Rev 22:21"]
    ["shorter", "Phlm.1.1", "Phm 1:1"]
    ["shorter", "Phlm.1.2", "Phm 2"]
    ["shorter", "Phlm.1.1-Phlm.1.2", "Phm 1\u20132"]
    ["shorter", "Phlm.1.3-Phlm.1.4", "Phm 3\u20134"]
    ["shorter", "Phlm.1.1-Heb.4.5", "Phm 1\u2014Heb 4:5"]
    ["shorter", "Phlm.1.2-Heb.4.5", "Phm 2\u2014Heb 4:5"]
    ["shorter", "Phlm.1.1-Heb.4", "Phm 1\u2014Heb 4:16"]
    ["shorter", "Phlm.1.2-Heb.4", "Phm 2\u2014Heb 4:16"]
    ["shorter", "Phlm.1.1-Heb", "Phm 1\u2014Heb 13:25"]
    ["shorter", "Phlm.1.2-Heb", "Phm 2\u2014Heb 13:25"]
    ["shorter", "Phlm-Heb", "Phm\u2014Heb"]
    ["shorter", "Phlm-Heb.6", "Phm 1\u2014Heb 6"]
    ["shorter", "Phlm-Heb.6.8", "Phm 1\u2014Heb 6:8"]
    ["shorter", "Phlm.1-Heb", "Phm 1\u2014Heb 13"]
    ["shorter", "Phlm.1-Heb.6", "Phm 1\u2014Heb 6"]
    ["shorter", "Phlm.1-Heb.6.8", "Phm 1\u2014Heb 6:8"]
    ["shorter", "Obad-Phlm", "Ob\u2014Phm"]
    ["shorter", "Obad-Phlm.1", "Ob 1\u2014Phm 1"]
    ["shorter", "Obad-Phlm.1.1", "Ob 1\u2014Phm 1:1"]
    ["shorter", "Obad-Phlm.1.2", "Ob 1\u2014Phm 2"]
    ["shorter", "Obad.1-Phlm", "Ob 1\u2014Phm 1"]
    ["shorter", "Obad.1-Phlm.1.1", "Ob 1\u2014Phm 1:1"]
    ["shorter", "Obad.1-Phlm.1.2", "Ob 1\u2014Phm 2"]
    ["shorter", "Obad.1.1-Phlm", "Ob 1\u2014Phm 25"]
    ["shorter", "Obad.1.1-Phlm.1", "Ob 1\u2014Phm 25"]
    ["shorter", "Obad.1.1-Phlm.1.1", "Ob 1\u2014Phm 1:1"]
    ["shorter", "Obad.1.1-Phlm.1.2", "Ob 1\u2014Phm 2"]
    ["shorter", "Obad.1.2-Phlm", "Ob 2\u2014Phm 25"]
    ["shorter", "Obad.1.2-Phlm.1", "Ob 2\u2014Phm 25"]
    ["shorter", "Obad.1.2-Phlm.1.1", "Ob 2\u2014Phm 1:1"]
    ["shorter", "Obad.1.2-Phlm.1.2", "Ob 2\u2014Phm 2"]
]

for test in tests
    [output_type, in_osis, expected] = test
    readable = osis_to_readable(output_type, in_osis)
    #check_osis = bcv.parse(in_osis).osis()
    #out_osis = bcv.parse(readable).osis()
    if expected isnt readable
        console.log in_osis, "expected", expected, "got", readable
    #if out_osis isnt check_osis
    #   console.log readable, "/", in_osis, "expected", check_osis, "got", out_osis
a-am commented 9 years ago

This script has been very helpful. Thank you. However when testing 1Cor.1.3-1Cor.1.10 it returns 1 Corinthians 3-10.

a-am commented 9 years ago

I believe I have found the issue. In the method is_single_chapter_book [start, end] = osis.split "-" should be changed to [start, end] = osis.split "."

openbibleinfo commented 9 years ago

When I take the code above and run osis_to_readable("long", "1Cor.1.3-1Cor.1.10"), it returns 1 Corinthians 1:3-10 for me. Your second comment suggests that maybe you're not using osis_compaction_strategy: "bcv"?

The is_single_chapter_book function basically checks whether, say, "1Cor 2" is a chapter (and thus has more than one chapter) or a verse, assuming that the "bcv" osis_compaction_strategy will return a range if it's a chapter. You could achieve the same result by splitting on the . and seeing whether the second element returned is equal to 1 (single-chapter book) or 2 (multi-chapter book). Then the function wouldn't rely on a specific osis_compaction_strategy.

a-am commented 9 years ago

How would I not be using the _osis_compactionstrategy: "bcv" if I am passing in a valid osis reference? I am using the same method with parameters osis_to_readable("long", "1Cor.1.3-1Cor.1.10") when testing the reference. Thank you for your time & code!

openbibleinfo commented 9 years ago

Here's the relevant line in the above code:

bcv.set_options osis_compaction_strategy: "bcv", book_alone_strategy: "full", book_sequence_strategy: "include", consecutive_combination_strategy: "combine", book_range_strategy: "include"

The osis_compaction_strategy controls the output from the bcv_parser, not its input; it affects how specific you want the output to be. So, given the input "Gen 1-50", a b strategy returns "Gen"; a "bc" strategy returns "Gen.1-Gen.50", and a "bcv" strategy returns "Gen.1.1-Gen.50.26". All are correct but are at varying levels of detail.

The above code uses "bcv" because "bcv" always returns a range if more than one verse is involved, which is convenient for several of the functions. The default is "b", which assumes you want the shortest available osis that can represent the input.

The above code also assumes that the other options are set as above; the output of osis_to_readable might be unexpected if they're set differently. Hope that helps!

openbibleinfo commented 8 years ago

For future readers of this topic, I've since created a repo that converts from OSIS to a human-readable format: https://github.com/openbibleinfo/Bible-Reference-Formatter