// ########### FROM SVN REVISION 3895 /****************************************************************************** * * versificationmgr.cpp - implementation of class VersificationMgr used * for managing versification systems * * $Id$ * * Copyright 2008-2013 CrossWire Bible Society (http://www.crosswire.org) * CrossWire Bible Society * P. O. Box 2528 * Tempe, AZ 85280-2528 * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * */ package org.crosswire.sword.mgr; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.crosswire.utils.Utils; import org.crosswire.xml.XMLBlock; import org.crosswire.xml.XMLDataElement; import org.crosswire.sword.keys.SWKey; public class VersificationMgr { private static Logger logger = Logger.getLogger(VersificationMgr.class); public static class Abbreviation implements Comparable { public Abbreviation(String ab, String osis) { this.ab = ab.toUpperCase(); this.osis = osis; } public String ab; public String osis; @Override public int compareTo(Abbreviation o) { return ab.compareTo(o.ab); } @Override public int hashCode() { return ab.hashCode(); } @Override public boolean equals(Object obj) { return (obj instanceof Abbreviation) ? ab.equals(((Abbreviation)obj).ab) : false; } }; public static class BookData { public BookData(String name, String osis, String prefAbbrev, int chapmax) { this.name = name; this.osis = osis; this.prefAbbrev = prefAbbrev; this.chapmax = chapmax; } /**Name of book */ String name; /**OSIS name */ String osis; /**Preferred Abbreviation */ String prefAbbrev; /**Maximum chapters in book */ int chapmax; /** Array[chapmax] of maximum verses in chapters */ int versemax[]; }; public static class Book { /** book name */ String longName; /** OSIS Abbreviation */ String osisName; /** Preferred Abbreviation */ String prefAbbrev; /** Maximum chapters in book */ int chapMax; void init() {} /** Array[chapmax] of maximum verses in chapters */ List verseMax = new ArrayList(); List offsetPrecomputed = new ArrayList(); // silly, but necessary for Collections.BinarySearch public Book(long firstOffsetPrecomputed) { offsetPrecomputed.add(firstOffsetPrecomputed); } public Book() { init(); } public Book(Book other) {} public void set(Book other) { longName = other.longName; osisName = other.osisName; prefAbbrev = other.prefAbbrev; chapMax = other.chapMax; init(); verseMax.clear(); int s = (int)other.verseMax.size(); if (s > 0) verseMax = other.verseMax; offsetPrecomputed = other.offsetPrecomputed; } Book(String longName, String osisName, String prefAbbrev, int chapMax) { this.longName = longName; this.osisName = osisName; this.prefAbbrev = prefAbbrev; this.chapMax = chapMax; init(); } public String getLongName() { return longName; } public String getOSISName() { return osisName; } public String getPreferredAbbreviation() { return prefAbbrev; } public int getChapterMax() { return chapMax; } public int getVerseMax(int chapter) { chapter--; return ((chapter < verseMax.size()) && (chapter > -1)) ? verseMax.get(chapter) : -1; } }; public static class BCV { public int book; public int chapter; public int verse; public char error; }; public static class System { /** Array[chapmax] of maximum verses in chapters */ private List books = new ArrayList(); private Map osisLookup = new HashMap(); /** General mapping rule is that first verse of every chapter corresponds first verse of another chapter in default intermediate canon(lxxnu), so mapping data contains expections. Intermediate canon could not contain corresponding data. Each element in @variable mappings contains of all rules that are related to particular book. @typedef mappingRule is a pointer on uchar[7]: 1 value - book id 1-based, ot+nt, 2-4 map to, 5-7 map from (chap,verse from, verse to if greater then "verse from"). Size of rule would be 8 if there is inter-book mapping and target book is abscent in reference system, in this case @variable mappingsExtraBooks data is used and id is rule book id minus book count. 0 - book 1 - to chap 2 - to verse min 3 - to verse max (if != 0) 4 - from chap 5 - from verse min 6 - from verse max (if != 0) * 7 - ? */ Mappings mappings = null; Abbreviation defaultAbbreviations[] = null; String name; int BMAX[] = new int[2]; long ntStartOffset; void init() { BMAX[0] = 0; BMAX[1] = 0; ntStartOffset = 0; } public System() { this.name = ""; init(); } public System(System other) { set(other); } public void set(System other) { init(); name = other.name; BMAX[0] = other.BMAX[0]; BMAX[1] = other.BMAX[1]; books = other.books; osisLookup = other.osisLookup; ntStartOffset = other.ntStartOffset; } public System(String name) { this.name = name; init(); } public String getName() { return name; } public int []getBMAX() { return BMAX; } public long getNTStartOffset() { return ntStartOffset; } public Book getBook(int number) { //logger.debug("books.size: " + books.size()); return (number < books.size()) ? books.get(number) : null; } public int getBookNumberByOSISName(String bookName) { return (osisLookup.containsKey(bookName)) ? osisLookup.get(bookName) : -1; } public Abbreviation []getDefaultAbbreviations() { return defaultAbbreviations; } public void setDefaultAbbreviations(Abbreviation abbrevs[]) { defaultAbbreviations = abbrevs; } public static class Mappings { public static class VerseMap { public int book = 0; // 0 public int toChapter = 0; // 1 public int toVerse = 0; // 2 public int toVerseMax = 0; // 3 (if != 0) public int fromChapter = 0; // 4 public int fromVerse = 0; // 5 public int fromVerseMax = 0; // 6 (if != 0) public int fromOtherBook = 0; // 7 (if != 0) }; List get(int bookNumber) { return map.get(bookNumber); } public List extraBooks = new ArrayList(); public Map> map = new HashMap>(); public Mappings() {} } void loadFromSBook(BookData ot[], BookData nt[], int chMax[], Mappings mappings) { int chap = 0; int book = 0; long offset = 0; // module heading offset++; // testament heading for (BookData bd : ot) { books.add(new Book(bd.name, bd.osis, bd.prefAbbrev, bd.chapmax)); offset++; // book heading Book b = books.get(books.size()-1); osisLookup.put(b.getOSISName(), books.size()); for (int i = 0; i < bd.chapmax; ++i) { b.verseMax.add(chMax[chap]); offset++; // chapter heading b.offsetPrecomputed.add(offset); offset += chMax[chap++]; } book++; } BMAX[0] = book; book = 0; ntStartOffset = offset; offset++; // testament heading for (BookData bd: nt) { books.add(new Book(bd.name, bd.osis, bd.prefAbbrev, bd.chapmax)); offset++; // book heading Book b = books.get(books.size()-1); osisLookup.put(b.getOSISName(), books.size()); for (int i = 0; i < bd.chapmax; ++i) { b.verseMax.add(chMax[chap]); offset++; // chapter heading b.offsetPrecomputed.add(offset); offset += chMax[chap++]; } book++; } BMAX[1] = book; // TODO: build offset speed array this.mappings = mappings; } int getBookCount() { return books.size(); } public long getOffsetFromVerse(int book, int chapter, int verse) { long offset = -1; chapter--; Book b = getBook(book); if (b == null) return -1; // assert we have a valid book if ((chapter > -1) && (chapter >= b.offsetPrecomputed.size())) return -1; // assert we have a valid chapter offset = b.offsetPrecomputed.get((chapter > -1)?chapter:0); if (chapter < 0) offset--; return (offset + verse); } public BCV getVerseFromOffset(long offset) { BCV retVal = new BCV(); if (offset < 1) { // just handle the module heading corner case up front (and error case) retVal.book = -1; retVal.chapter = 0; retVal.verse = 0; retVal.error = (char) offset; return retVal; // < 0 = error } // binary search for book int indexOfB = java.util.Collections.binarySearch(books, new Book(offset), new Comparator() { public int compare(Book o1, Book o2) { return (int) (o1.offsetPrecomputed.get(0) - o2.offsetPrecomputed.get(0)); } }); if (indexOfB < 0) { indexOfB=(indexOfB*-1)-1 ; } if (indexOfB > books.size()-1) indexOfB = books.size()-1; retVal.book = indexOfB+1; Book b = books.get(indexOfB); if (offset < b.offsetPrecomputed.get(0)-((((retVal.book == 0) || retVal.book == BMAX[0]+1))?2:1)) { // -1 for chapter headings retVal.book--; if (indexOfB > 0) { b = books.get(--indexOfB); } } int indexOfC = java.util.Collections.binarySearch(b.offsetPrecomputed, offset); // if we're a book heading, we are lessthan chapter precomputes, but greater book. This catches corner case. if (indexOfC < 0) { indexOfC=(indexOfC*-1)-1; } if (indexOfC > b.offsetPrecomputed.size()-1) indexOfC = b.offsetPrecomputed.size()-1; long c = b.offsetPrecomputed.get(indexOfC); if ((offset < c) && (indexOfC == 0)) { retVal.chapter = (int) ((offset - c)+1); // should be 0 or -1 (for testament heading) retVal.verse = 0; } else { if (offset < c) { indexOfC--; c = b.offsetPrecomputed.get(indexOfC); } retVal.chapter = indexOfC+1; retVal.verse = (int) (offset - c); } retVal.error = ((retVal.chapter > 0) && (retVal.verse > b.getVerseMax(retVal.chapter))) ? SWKey.KEYERR_OUTOFBOUNDS : 0; return retVal; } public static class TranslatedVerse { public TranslatedVerse(String book, int chapter, int verse, int verseEnd) { this.book = book; this.chapter = chapter; this.verse = verse; this.verseEnd = verseEnd; } public String book; public int chapter; public int verse; public int verseEnd; }; public void translateVerse(System dstSys, TranslatedVerse translatedVerse) { if ("LXXNU".equals(getName())) { if ("LXXNU".equals(dstSys.getName())) { return; } // reversed mapping int b = dstSys.getBookNumberByOSISName(translatedVerse.book)-1; if (b < 0) { for (int i = 0; i < dstSys.mappings.extraBooks.size(); ++i) { if (translatedVerse.book != null && translatedVerse.book.equals(dstSys.mappings.extraBooks.get(i))) { b = books.size()+i-2; break; } } } List maps = dstSys.mappings.get(b); if (maps == null) { return; } Mappings.VerseMap a = null; // reversed mapping should use forward search for item for (Mappings.VerseMap map : maps) { if (map.book != b+1) continue; // filter inter-book rules if (map.fromChapter == translatedVerse.chapter && map.fromVerse <= translatedVerse.verse) { if (map.fromVerse == translatedVerse.verse || (map.fromVerseMax >= translatedVerse.verse && map.fromVerse <= translatedVerse.verse)) { // inside of any mapping range translatedVerse.chapter = map.toChapter; translatedVerse.verse = map.toVerse; translatedVerse.verseEnd = map.toVerseMax; if (map.book >= dstSys.books.size()) { // SWLog::getSystemLog()->logWarning("map to extra books, possible bug source\n"); translatedVerse.book = dstSys.getBook(map.fromOtherBook-1).getOSISName(); } return; } // destination mapping can have duplicate items, use the last (by using <=) if (a == null || (a.fromVerse > a.fromVerseMax ? a.fromVerse : a.fromVerseMax) <= (map.fromVerse > map.fromVerseMax ? map.fromVerse : map.fromVerseMax)) { a = map; } } } if (a != null) { //dbg_mapping SWLOGD("set appropriate: %i %i %i %i %i %i\n",a[1],a[2],a[3],a[4],a[5],a[6]); translatedVerse.chapter = a.toChapter; // shift verse int d = (a.toVerseMax > a.toVerse ? a.toVerseMax : a.toVerse) - (a.fromVerseMax > a.fromVerse ? a.fromVerseMax : a.fromVerse); if (translatedVerse.verse < translatedVerse.verseEnd) { translatedVerse.verseEnd += d; } else { translatedVerse.verseEnd = translatedVerse.verse + d; } translatedVerse.verse += d; if (a.book > dstSys.books.size()) { translatedVerse.book = dstSys.getBook(a.fromOtherBook-1).getOSISName(); } return; } //dbg_mapping SWLOGD("There is no mapping.\n"); } else if (!"LXXNU".equals(dstSys.getName())) { System base = getSystemVersificationMgr().getVersificationSystem("LXXNU"); int srcVerse = translatedVerse.verse; translateVerse(base, translatedVerse); TranslatedVerse interimVerse = new TranslatedVerse(translatedVerse.book, translatedVerse.chapter, translatedVerse.verse, translatedVerse.verseEnd); base.translateVerse(dstSys, translatedVerse); // contraction->expansion fix if (translatedVerse.verse < translatedVerse.verseEnd && !(interimVerse.verse < interimVerse.verseEnd)) { base.translateVerse(this, interimVerse); if (interimVerse.verse < interimVerse.verseEnd) { translatedVerse.verse += srcVerse - interimVerse.verse; if (translatedVerse.verse > translatedVerse.verseEnd) { translatedVerse.verse = translatedVerse.verseEnd; } else { translatedVerse.verseEnd = translatedVerse.verse; } } } } else { /* 0 map.add(m.book); 1 map.add(m.toChapter); 2 map.add(m.toVerse); 3 map.add(m.toVerseMax); 4 map.add(m.fromChapter); 5 map.add(m.fromVerse); 6 map.add(m.fromVerseMax); 7 fromOtherBook */ //dbg_mapping SWLOGD("Perform forward mapping.\n"); int b = getBookNumberByOSISName(translatedVerse.book)-1; List maps = mappings.get(b); if (maps == null) { return; } // forward mapping should use reversed search for item for (int i = maps.size() - 1; i >= 0; --i) { Mappings.VerseMap m = maps.get(i); if (m.toChapter < translatedVerse.chapter) { // SWLog.getSystemLog().logWarning("There is no mapping for this chapter.\n"); return; } if (m.toChapter == translatedVerse.chapter && m.toVerse <= translatedVerse.verse) { //dbg_mapping SWLOGD("found mapping %i %i %i %i %i %i\n",m[1],m[2],m[3],m[4],m[5],m[6]); if (m.toVerse == translatedVerse.verse || (m.toVerseMax >= translatedVerse.verse && m.toVerse <= translatedVerse.verse)) { translatedVerse.chapter = m.fromChapter; translatedVerse.verse = m.fromVerse; translatedVerse.verseEnd = m.fromVerseMax; } else { translatedVerse.chapter = m.fromChapter; // shift verse int d = (m.fromVerseMax > m.fromVerse ? m.fromVerseMax : m.fromVerse) - (m.toVerseMax > m.toVerse ? m.toVerseMax : m.toVerse); if (translatedVerse.verse < translatedVerse.verseEnd) { translatedVerse.verseEnd += d; } else { translatedVerse.verseEnd = translatedVerse.verse+d; } translatedVerse.verse += d; } if (m.book > books.size()) { translatedVerse.book = mappings.extraBooks.get(m.book - books.size() - 1); } return; } } //dbg_mapping SWLOGD("No mapping.\n"); } } }; public VersificationMgr() {} public VersificationMgr(VersificationMgr other) { set(other); } public void set(VersificationMgr other) { systems = other.systems; } static VersificationMgr systemVersificationMgr = null; public void registerVersificationSystemFromXML(String xmlFilePath) throws FileNotFoundException { logger.info("registering v11n: " + xmlFilePath); List otbooks = new ArrayList(); List ntbooks = new ArrayList(); List vm = new ArrayList(); LinkedHashSet defaultAbbrevs = new LinkedHashSet(); XMLBlock v11n = XMLBlock.loadXMLBlock(new FileInputStream(xmlFilePath)); XMLBlock ot = v11n.getBlock("collection", "collectionID", "1"); XMLBlock nt = v11n.getBlock("collection", "collectionID", "2"); XMLBlock abbrevs = v11n.getBlock("abbreviations"); XMLBlock mappingsBlock = v11n.getBlock("mappings"); VersificationMgr.System.Mappings mappings = new VersificationMgr.System.Mappings(); logger.info("abbrevs: " + abbrevs); if (ot != null) { for (XMLBlock b : ot.getBlocks("book")) { otbooks.add(new BookData(b.getAttribute("name"), b.getAttribute("osisID"), b.getAttribute("preferredAbbrev"), Integer.parseInt(b.getAttribute("chapterMax")))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("name"), b.getAttribute("osisID"))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("osisID"), b.getAttribute("osisID"))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("preferredAbbrev"), b.getAttribute("osisID"))); for (XMLBlock c : b.getBlocks("chapter")) { vm.add(Integer.parseInt(c.getAttribute("verseMax"))); } } } if (nt != null) { for (XMLBlock b : nt.getBlocks("book")) { ntbooks.add(new BookData(b.getAttribute("name"), b.getAttribute("osisID"), b.getAttribute("preferredAbbrev"), Integer.parseInt(b.getAttribute("chapterMax")))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("name"), b.getAttribute("osisID"))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("osisID"), b.getAttribute("osisID"))); defaultAbbrevs.add(new Abbreviation(b.getAttribute("preferredAbbrev"), b.getAttribute("osisID"))); for (XMLBlock c : b.getBlocks("chapter")) { vm.add(Integer.parseInt(c.getAttribute("verseMax"))); } } } if (abbrevs != null) { for (XMLBlock b : abbrevs.getBlocks("abbr")) { Abbreviation a = new Abbreviation(new XMLDataElement(b).getText(), b.getAttribute("osisID")); logger.info("adding abbr: " + a); defaultAbbrevs.add(a); } } int vmInts[] = new int[vm.size()]; int i = 0; for (Integer e : vm) vmInts[i++] = e.intValue(); List defaultAbbs = new ArrayList(defaultAbbrevs); Collections.sort(defaultAbbs); if (mappingsBlock != null) { XMLBlock extraBooks = mappingsBlock.getBlock("extraBooks"); if (extraBooks != null) { for (XMLBlock b : extraBooks.getBlocks("book")) { mappings.extraBooks.add(b.getAttribute("name")); } } for (XMLBlock m : mappingsBlock.getBlocks("map")) { System.Mappings.VerseMap map = new System.Mappings.VerseMap(); try { map.book = Integer.parseInt(m.getAttribute("book")); } catch(Exception e) {} try { map.toChapter = Integer.parseInt(m.getAttribute("toChapter")); } catch(Exception e) {} try { map.toVerse = Integer.parseInt(m.getAttribute("toVerse")); } catch(Exception e) {} try { map.toVerseMax = Integer.parseInt(m.getAttribute("toVerseMax")); } catch(Exception e) {} try { map.fromChapter = Integer.parseInt(m.getAttribute("fromChapter")); } catch(Exception e) {} try { map.fromVerse = Integer.parseInt(m.getAttribute("fromVerse")); } catch(Exception e) {} try { map.fromVerseMax = Integer.parseInt(m.getAttribute("fromVerseMax")); } catch(Exception e) {} try { map.fromOtherBook = Integer.parseInt(m.getAttribute("fromOtherBook")); } catch(Exception e) {} List bookMaps = mappings.map.get(map.book-1); if (bookMaps == null) { bookMaps = new ArrayList(); mappings.map.put(map.book-1, bookMaps); } bookMaps.add(map); } } registerVersificationSystem(v11n.getAttribute("v11nID"), otbooks.toArray(new BookData[0]), ntbooks.toArray(new BookData[0]), vmInts, mappings, (defaultAbbs.size() > 0) ? defaultAbbs.toArray(new Abbreviation[0]) : null); } public static VersificationMgr getSystemVersificationMgr() { if (systemVersificationMgr == null) { systemVersificationMgr = new VersificationMgr(); systemVersificationMgr.registerVersificationSystem("KJV", CanonKJV.otbooks, CanonKJV.ntbooks, CanonKJV.vm); // systemVersificationMgr.registerVersificationSystem("Leningrad", otbooks_leningrad, ntbooks_null, vm_leningrad); // systemVersificationMgr.registerVersificationSystem("MT", otbooks_mt, ntbooks_null, vm_mt); systemVersificationMgr.registerVersificationSystem("KJVA", CanonKJVA.otbooks, CanonKJV.ntbooks, CanonKJVA.vm); systemVersificationMgr.registerVersificationSystem("LXXNU", CanonLXXNU.otbooks, CanonLXXNU.ntbooks, CanonLXXNU.vm); // systemVersificationMgr.registerVersificationSystem("NRSV", otbooks, ntbooks, vm_nrsv); // systemVersificationMgr.registerVersificationSystem("NRSVA", otbooks_nrsva, ntbooks, vm_nrsva); // systemVersificationMgr.registerVersificationSystem("Synodal", otbooks_synodal, ntbooks_synodal, vm_synodal); // systemVersificationMgr.registerVersificationSystem("Vulg", otbooks_vulg, ntbooks_vulg, vm_vulg); // systemVersificationMgr.registerVersificationSystem("German", otbooks_german, ntbooks, vm_german); // systemVersificationMgr.registerVersificationSystem("Luther", otbooks_luther, ntbooks_luther, vm_luther); // systemVersificationMgr.registerVersificationSystem("Catholic", otbooks_catholic, ntbooks, vm_catholic); // systemVersificationMgr.registerVersificationSystem("Catholic2", otbooks_catholic2, ntbooks, vm_catholic2); // systemVersificationMgr.registerVersificationSystem("SynodalP", otbooks, ntbooks, vm_synodalp); try { String v11nsString = Utils.getSysConfig().getProperty("AddVersifications"); if (v11nsString != null) { for (String v11n : v11nsString.split(";")) { try { systemVersificationMgr.registerVersificationSystemFromXML(v11n); } catch (FileNotFoundException e) { logger.error("Error loading Versification from XML file. File not found: " + v11n); } } } } catch (Exception e) { e.printStackTrace(); } // it's ok if we can't find or load additional v11ns } return systemVersificationMgr; } /* struct BookOffsetLess { bool operator() (VersificationMgr::Book &o1, VersificationMgr::Book &o2) { return o1.p.offsetPrecomputed[0] < o2.p.offsetPrecomputed[0]; } bool operator() (long &o1, VersificationMgr::Book &o2) { return o1 < o2.p.offsetPrecomputed[0]; } bool operator() (VersificationMgr::Book &o1, long &o2) { return o1.p.offsetPrecomputed[0] < o2; } bool operator() (long &o1, long &o2) { return o1 < o2; } }; */ Map systems = new HashMap(); public static void setSystemVersificationMgr(VersificationMgr newVersificationMgr) { systemVersificationMgr = newVersificationMgr; } public System getVersificationSystem(String name) { return systems.get(name); } public Set getVersificationSystems() { return systems.keySet(); } void registerVersificationSystem(String name, BookData ot[], BookData nt[], int chMax[]) { registerVersificationSystem(name, ot, nt, chMax, null, null); } void registerVersificationSystem(String name, BookData ot[], BookData nt[], int chMax[], System.Mappings mappings) { registerVersificationSystem(name, ot, nt, chMax, mappings, null); } void registerVersificationSystem(String name, BookData ot[], BookData nt[], int chMax[], Abbreviation abbrevs[]) { registerVersificationSystem(name, ot, nt, chMax, null, abbrevs); } void registerVersificationSystem(String name, BookData ot[], BookData nt[], int chMax[], System.Mappings mappings, Abbreviation abbrevs[]) { systems.put(name, new System(name)); System s = systems.get(name); s.loadFromSBook(ot, nt, chMax, mappings); s.setDefaultAbbreviations(abbrevs); } public static void main(String args[]) { java.lang.System.out.println(VersificationMgr.getSystemVersificationMgr().getVersificationSystem("LXXNU").getBook(4).getVerseMax(3)); } }